package com.lamb.script.core.executor;

import com.lamb.script.core.executor.loader.ScriptClassLoader;
import com.lamb.script.core.executor.loader.ScriptResourceLoader;
import com.lamb.script.core.utils.NoClosePrintStream;
import com.lamb.script.core.utils.ScriptConsole;
import com.lamb.script.core.utils.HttpUtil;
import com.lamb.script.core.utils.ScriptUtil;
import jdk.internal.dynalink.beans.StaticClass;

import javax.script.*;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;

/**
 * 任务处理器.
 *
 * @author lamb
 * @date 2018/1/24
 */
public class ScriptEngineCallable implements Callable, AutoCloseable {

    private static final ScriptEngineManager SCRIPT_ENGINE_MANAGER = new ScriptEngineManager();

    private final ScriptEngine engine;
    private final ScriptClassLoader classLoader;
    private final ScriptResourceLoader resourceLoader;
    private final ScriptContext scriptContext;
    private final ScriptConsole console;
    private final IScriptContentProvider provider;
    /**
     * 参数.
     */
    private final Map<String, Object> bindings = new HashMap<>(16);

    /**
     * 原始的类加载器.
     */
    private final ClassLoader srcClassLoader;

    /**
     * 是否关闭.
     */
    private boolean closed = false;

    public ScriptEngineCallable(IScriptContentProvider provider) {
        this.provider = provider;
        this.engine = SCRIPT_ENGINE_MANAGER.getEngineByName("javascript");
        // 加载资源工具.
        this.resourceLoader = new ScriptResourceLoader(this);
        // 初始化客户端.
        PrintStream out = new NoClosePrintStream(System.out);
        this.console = new ScriptConsole(out, out);
        // 初始化上下文.
        this.scriptContext = new SimpleScriptContext();
        scriptContext.setBindings(new SimpleBindings(), ScriptContext.GLOBAL_SCOPE);

        // 初始化class工具.
        this.srcClassLoader = Thread.currentThread().getContextClassLoader();
        this.classLoader = new ScriptClassLoader(new URL[0],
                srcClassLoader, console);
        Thread.currentThread().setContextClassLoader(classLoader);

        // 初始化环境.
        scriptContext.setWriter(new PrintWriter(getConsole().out, true));
        scriptContext.setErrorWriter(new PrintWriter(getConsole().err, true));
        this.initEngine();
    }

    /**
     * 初始化引擎.
     */
    private void initEngine() {
        setAttribute("console", console);
        setAttribute("scriptEngineCallable", this);
        setAttribute("scriptClassLoader", classLoader);
        setAttribute("scriptResourceLoader", resourceLoader);
        setAttribute("util", StaticClass.forClass(ScriptUtil.class));
        setAttribute("http", StaticClass.forClass(HttpUtil.class));
    }

    /**
     * 设置参数.
     *
     * @param key   key.
     * @param value value.
     */
    public void set(String key, Object value) {
        bindings.put(key, value);
    }

    public ScriptClassLoader getClassLoader() {
        return classLoader;
    }

    public ScriptResourceLoader getResourceLoader() {
        return resourceLoader;
    }

    public ScriptConsole getConsole() {
        return console;
    }

    @Override
    public Object call() throws Exception {
        // 初始化 bindings .
        for (Map.Entry<String, Object> entry : bindings.entrySet()) {
            setAttribute(entry.getKey(), entry.getValue());
        }
        try {
            resourceLoader.importScripts("classpath:/base.js");
            String scriptName = provider.getScriptContent("js");
            if (ScriptUtil.isBlank(scriptName)) {
                throw new RuntimeException("scriptName not exists");
            }
            String script;
            // 约定以@js:开头则直接认定为js脚本内容.
            if (scriptName.startsWith(ScriptUtil.FLAG_JS_CONTENT)) {
                script = scriptName.substring(ScriptUtil.FLAG_JS_CONTENT.length());
            } else {
                script = resourceLoader.getScriptContent(scriptName);
                if (ScriptUtil.isBlank(script)) {
                    throw new RuntimeException(scriptName + " not config");
                }
            }

            return eval(script);
        } finally {
            close();
        }
    }

    /**
     * 执行.
     *
     * @param scriptContent 脚本内容.
     * @return 执行结果.
     * @throws ScriptException 异常.
     */
    public Object eval(String scriptContent) throws ScriptException {
        return engine.eval(scriptContent, scriptContext);
    }

    /**
     * 设置属性.
     *
     * @param name  属性名称.
     * @param value 值.
     */
    public void setAttribute(String name, Object value) {
        scriptContext.setAttribute(name, value, ScriptContext.GLOBAL_SCOPE);
    }

    @Override
    public void close() {
        if (closed) {
            return;
        }
        closed = true;
        // 清理out.err.
        console.flush();
        console.clear();
        // 还原classLoader.
        Thread.currentThread().setContextClassLoader(srcClassLoader);
    }
}
